package nl.vu.few.ibivu.biomodels.server.petrinet;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import nl.vu.few.ibivu.biomodels.shared.PetrifierArc;
import nl.vu.few.ibivu.biomodels.shared.PetrifierArcType;
import nl.vu.few.ibivu.biomodels.shared.PetrifierModelData;
import nl.vu.few.ibivu.biomodels.shared.PetrifierNode;
import nl.vu.few.ibivu.biomodels.shared.PetrifierNodeType;
import nl.vu.few.ibivu.jlemon.ArcMap;
import nl.vu.few.ibivu.jlemon.Graphs;
import nl.vu.few.ibivu.jlemon.NodeMap;
import nl.vu.few.ibivu.jlemon.listgraph.ListPetriNet;
import nl.vu.few.ibivu.jlemon.listgraph.Node;

public class Petrifier extends ListPetriNet<Integer> {
	private int capacity;
	private ArcMap<Arc, ArcData> arcData = new ArcMap<Arc, ArcData>(this);
	private NodeMap<Node, NodeData> nodeData = new NodeMap<Node, NodeData>(this);
	
	public Petrifier() {
		this(1);
	}
	public Petrifier(int capacity) {
		if (capacity < 1) {
			throw new IllegalArgumentException();
		}
		this.capacity = capacity;
		
		this.addAddArcHandler(arcData);
		this.addEraseArcHandler(arcData);
		this.addClearHandler(arcData);
		
		this.addAddNodeHandler(nodeData);
		this.addEraseNodeHandler(nodeData);
		this.addClearHandler(nodeData);
	}
	
	@Override
	public boolean isEnabled(Map<Node, Integer> marking, Node transition) {
		if (!isTransition(transition))
			return false;
		Iterator<Node> inIterator = this.inputPlaceIterator(transition);
		// check preconditions
		while(inIterator.hasNext()) {
			Node place = inIterator.next();
			Arc arc = Graphs.findArc(this, place, transition, null);
			// if (p) --a--> |t| AND M(p) < weight(a) then t is not enabled
			if (isActivation(arc)) {
				if (marking.get(place) < arcData.get(arc).getWeight()) {
					return false;
				}
			}
			// if (p) --a--| |t| AND M(p) >= weight(a) then t is not enabled
			else {
				if (marking.get(place) >= arcData.get(arc).getWeight()) {
					return false;
				}
			}
		}
		Iterator<Node> outIterator = this.outputPlaceIterator(transition);
		// check postconditions
		// FIXME what happens if there are multiple output places and some of them can't fire?
		while(outIterator.hasNext()) {
			Node place = outIterator.next();
			Arc arc = Graphs.findArc(this, transition, place, null);
			
			// if |t| --a--> (p) AND M(p) + weight(a) > capacity then t is not enabled
			if (isActivation(arc)) {
				if (marking.get(place) + arcData.get(arc).getWeight() > capacity) {
					return false;
				}
			}
			// if |t| --a--| (p) AND M(p) - weight(a) < 0 then t is not enabled
			else {
				if (marking.get(place) - arcData.get(arc).getWeight() < 0) {
					return false;
				}
			}
		}
		return true;
	}
	
	public boolean isActivation(Arc arc) {
		return PetrifierArcType.ACTIVATION.equals(arcData.get(arc).getType());
	}
	
	public boolean isInhibition(Arc arc) {
		return PetrifierArcType.INHIBITION.equals(arcData.get(arc).getType());
	}

	@Override
	public void fire(Map<Node, Integer> marking, Node transition) {
		if (!isEnabled(marking, transition))
			return;
		Iterator<Node> outIterator = this.outputPlaceIterator(transition);
		// change postconditions markings
		while(outIterator.hasNext()) {
			Node place = outIterator.next();
			Arc arc = Graphs.findArc(this, transition, place, null);
			
			// if |t| --a--> (p) then M'(p) = M(p) + weight(a)
			if (isActivation(arc)) {
				marking.put(place, marking.get(place) + arcData.get(arc).getWeight());
			}
			// if |t| --a--> (p) then M'(p) = M(p) - weight(a)
			else {
				marking.put(place, marking.get(place) - arcData.get(arc).getWeight());
			}
		}
	}
	
	public Petrifier build(PetrifierModelData data) {
		Map<String, Node> xref = new HashMap<String, Node>();
		
		this.clear();
		
		for (PetrifierNode dNode : data.getNodes()) {
			Node node = this.addNode();
			nodeData.put(node, new NodeData(dNode.getId(), dNode.getType(), dNode.getLabel(), dNode.getUniprotId()));
			xref.put(dNode.getId(), node);
		}
		
		for (PetrifierArc dArc : data.getEdges()) {
			Arc arc = this.addArc(xref.get(dArc.getSource()), xref.get(dArc.getTarget()));
			arcData.put(arc, new ArcData(dArc.getType(), dArc.getWeight()));
		}
		
		return this;
	}
	
	public Map<Node, Integer> getEmptyMarking() {
		NodeMap<Node, Integer> map = new NodeMap<Node, Integer>(this, 0);
		this.addAddNodeHandler(map);
		this.addEraseNodeHandler(map);
		this.addClearHandler(map);
		return map;
	}
	
	private class ArcData {
		private int weight = 1;
		private PetrifierArcType type = PetrifierArcType.ACTIVATION;
		public ArcData() {}
		public ArcData(PetrifierArcType type) {
			this();
			this.type = type;
		}
		public ArcData(PetrifierArcType type, int weight) {
			this(type);
			this.weight = weight;
		}
		public int getWeight() {
			return weight;
		}
		@SuppressWarnings("unused")
		public ArcData setWeight(int weight) {
			this.weight = weight;
			return this;
		}
		public PetrifierArcType getType() {
			return type;
		}
		@SuppressWarnings("unused")
		public ArcData setType(PetrifierArcType type) {
			this.type = type;
			return this;
		}
	}
	
	private class NodeData {
		private PetrifierNodeType type;
		private String id;
		private String label;
		private String uniprotId;
		public NodeData(String id, PetrifierNodeType type, String label, String uniprotId) {
			super();
			this.id = id;
			this.type = type;
			this.label = label;
			this.uniprotId = uniprotId;
		}
		public String getLabel() {
			return label;
		}
		@SuppressWarnings("unused")
		public void setLabel(String label) {
			this.label = label;
		}
		public String getUniprotId() {
			return uniprotId;
		}
		@SuppressWarnings("unused")
		public void setUniprotId(String uniprotId) {
			this.uniprotId = uniprotId;
		}
		public PetrifierNodeType getType() {
			return type;
		}
		@SuppressWarnings("unused")
		public void setType(PetrifierNodeType type) {
			this.type = type;
		}
		public String getId() {
			return id;
		}
		@SuppressWarnings("unused")
		public void setId(String id) {
			this.id = id;
		}
	}
	
	public String getOriginalId(Node node) {
		NodeData data;
		if ((data = nodeData.get(node)) != null) {
			return data.getId();
		}
		return null;
	}
	
	public String getLabel(Node node) {
		NodeData data;
		if ((data = nodeData.get(node)) != null) {
			return data.getLabel();
		}
		return null;
	}
	
	public String getUniprotId(Node node) {
		NodeData data;
		if ((data = nodeData.get(node)) != null) {
			return data.getUniprotId();
		}
		return null;
	}

	@Override
	public boolean isPlace(Node node) {
		return PetrifierNodeType.COMPONENT.equals(nodeData.get(node).getType());
	}
	
	@Override
	public boolean isTransition(Node node) {
		return PetrifierNodeType.INTERACTION.equals(nodeData.get(node).getType());
	}
	
	public int getCapacity() {
		return capacity;
	}
}
